package org.ifi.xechecs.board;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;


import org.ifi.xechecs.transtable.Zobrist;


public class Board {
	public static final byte LENGTH = 8;
	private byte[][] fields;
	private byte enPassant = -1;
	private Move lastMove = null;
	private boolean isWhiteToMove = true;
	private boolean isCastledWhite = false;
	private boolean isCastledBlack = false;
	private boolean isRookA1Moved = false;
	private boolean isRookA8Moved = false;
	private boolean isRookH1Moved = false;
	private boolean isRookH8Moved = false;
	private boolean isWhiteKingMoved = false;
	private boolean isBlackKingMoved = false;
	private long hash;
	
	public byte getCurrentPlayer(){
		return isWhiteToMove? Piece.WHITE: Piece.BLACK;
	}
	public Board(){
		fields = new byte[LENGTH][LENGTH];
		//initialize for White player
		setPiece(0, 0, Piece.WR);
		setPiece(0, 1, Piece.WN);
		setPiece(0, 2, Piece.WB);
		setPiece(0, 3, Piece.WQ);
		setPiece(0, 4, Piece.WK);
		setPiece(0, 5, Piece.WB);
		setPiece(0, 6, Piece.WN);
		setPiece(0, 7, Piece.WR);
		for(int i = 0; i < LENGTH; i++){
			setPiece(1, i, Piece.WP);
		}
		setPiece(LENGTH - 1, 0, Piece.BR);
		setPiece(LENGTH - 1, 1, Piece.BN);
		setPiece(LENGTH - 1, 2, Piece.BB);
		setPiece(LENGTH - 1, 3, Piece.BQ);
		setPiece(LENGTH - 1, 4, Piece.BK);
		setPiece(LENGTH - 1, 5, Piece.BB);
		setPiece(LENGTH - 1, 6, Piece.BN);
		setPiece(LENGTH - 1, 7, Piece.BR);
		for(int i = 0; i < LENGTH; i++){
			setPiece(LENGTH - 2, i, Piece.BP);
		}
		this.setHash();
	}
	
	public Board(Board b){
		this.fields = new byte[LENGTH][LENGTH];
		for(int i=0; i< LENGTH; i++){
			for(int j = 0; j < LENGTH; j++){
				this.fields[i][j]= b.getPiece(i, j);
			}
		}
//		fields = Arrays.copyOf(b.fields, b.fields.length);
		this.isCastledWhite = b.isCastledWhite;
		this.isCastledBlack = b.isCastledBlack;
		this.isRookA1Moved = b.isRookA1Moved;
		this.isRookA8Moved = b.isRookA8Moved;
		this.isRookH1Moved = b.isRookH1Moved;
		this.isRookH8Moved = b.isRookH8Moved;
		this.isWhiteKingMoved = b.isWhiteKingMoved;
		this.isBlackKingMoved = b.isBlackKingMoved;
//System.out.println("Copy en passant "+ b.getEnPassant());		
		this.enPassant = b.getEnPassant();
		
		this.lastMove = b.getLastMove();
		this.setHash();
	}
	
	public final void copyFrom(Board b) {
		this.fields = Arrays.copyOf(b.fields, b.fields.length);
		this.isWhiteToMove = b.isWhiteToMove;
		this.isCastledWhite = b.isCastledWhite;
		this.isCastledBlack = b.isCastledBlack;
		this.isRookA1Moved = b.isRookA1Moved;
		this.isRookA8Moved = b.isRookA8Moved;
		this.isRookH1Moved = b.isRookH1Moved;
		this.isRookH8Moved = b.isRookH8Moved;
		this.isWhiteKingMoved = b.isWhiteKingMoved;
		this.isBlackKingMoved = b.isBlackKingMoved;
		
		this.enPassant = b.getEnPassant();
		this.lastMove = b.getLastMove();		
	}
	
	public Board applyMove(Move move){
		//implement
		Board board = new Board(this);
	
		Position from = move.getFrom();
		Position to = move.getTo();
		
		//for king
		//e1 && g1
		if (board.getPiece(from) == Piece.WK && from.equalPosition("e1")&& to.equalPosition("g1")) { // white castling short
			board.setPiece(to, Piece.WK);
			board.setPiece(from, Piece.NONE);
			board.setPiece(Position.getPosition("h1"), Piece.NONE);
			board.setPiece(Position.getPosition("f1"), Piece.WR);
			board.isWhiteKingMoved = true;
			board.isRookH1Moved = true;
			board.isCastledWhite = true;
			board.setLastMove(move);
			board.toggleWhiteToMove();
			//for en passant
			board.setEnPassant((byte)-1);
			return board;
		}
		// e1, c1
		if (board.getPiece(from) == Piece.WK && from.equalPosition("e1") && to.equalPosition("c1")) { // white castling long
			board.setPiece(to, Piece.WK);
			board.setPiece(from, Piece.NONE);
			board.setPiece(Position.getPosition("a1"), Piece.NONE);
			board.setPiece(Position.getPosition("d1"), Piece.WR);
			board.isWhiteKingMoved = true;
			board.isRookA1Moved = true;
			board.isCastledWhite = true;
			board.setLastMove(move);
			board.toggleWhiteToMove();
			//for en passant
			board.setEnPassant((byte)-1);
			return board;
		}
		// e8, g8
		if (board.getPiece(from) == Piece.BK && from.equalPosition("e8") && to.equalPosition("g8")) { // black castling short
			board.setPiece(to, Piece.BK);
			board.setPiece(from, Piece.NONE);
			board.setPiece(Position.getPosition("h8"), Piece.NONE);
			board.setPiece(Position.getPosition("f8"), Piece.BR);
			board.isBlackKingMoved = true;
			board.isRookH8Moved = true;
			board.isCastledBlack = true;
			board.setLastMove(move);
			board.toggleWhiteToMove();
			//for en passant
			board.setEnPassant((byte)-1);
			return board;
		}
		// e8 c8
		if (board.getPiece(from) == Piece.BK && from.equalPosition("e8") && to.equalPosition("c8")) { // black castling long
			board.setPiece(to, Piece.BK);
			board.setPiece(from, Piece.NONE);
			board.setPiece(Position.getPosition("a8"), Piece.NONE);
			board.setPiece(Position.getPosition("d8"), Piece.BR);
			board.isBlackKingMoved = true;
			board.isRookA8Moved = true;
			board.isCastledBlack = true;
			board.setLastMove(move);
			board.toggleWhiteToMove();
			//for en passant
			board.setEnPassant((byte)-1);
			return board;
		}
		
		//En passant move
		//en passant for White move
		if (board.getPiece(move.getFrom())== Piece.WP &&
				move.getTo().toByte() == board.getEnPassant()){			
			board.setPiece( new Position((byte)(board.getEnPassant() - 8 )), Piece.NONE);
			
		} else if (board.getPiece(move.getFrom())== Piece.BP &&
				move.getTo().toByte() == board.getEnPassant()){	
			board.setPiece( new Position((byte)(board.getEnPassant() + 8 )), Piece.NONE);			
		}
		// En passant for White move
		if (board.getPiece(move.getFrom()) == Piece.WP
				&& ((move.getTo().getRow() - move.getFrom().getRow()) == 2)) {
			board.setEnPassant((byte) (move.getFrom().toByte() + 8));

		} else if (board.getPiece(move.getFrom()) == Piece.BP
				&& ((move.getTo().getRow() - move.getFrom().getRow()) == -2)) {

			board.setEnPassant((byte) (move.getFrom().toByte() - 8));
		} else {
			board.setEnPassant((byte)-1);
		}
		
		//Defaut move
		board.setPiece(move.getTo(), getPiece(move.getFrom()));
		board.setPiece(move.getFrom(), Piece.NONE);
//System.out.println("#board: en passant value: " + getEnPassant());		//set en passant default
		
		
		if (move.getFrom().equalPosition("e1"))
			board.isWhiteKingMoved = true;
		if (move.getFrom().equalPosition("e8"))
			board.isBlackKingMoved = true;
		if (move.getFrom().equalPosition("a1"))
			board.isRookA1Moved = true;
		if (move.getFrom().equalPosition("a8"))
			board.isRookA8Moved = true;
		if (move.getFrom().equalPosition("h1"))
			board.isRookH1Moved = true;
		if (move.getFrom().equalPosition("h8"))
			board.isRookH8Moved = true;
		
		//Bug with promotion
		if (board.getPiece(move.getTo())== Piece.WP || board.getPiece(move.getTo()) == Piece.BP) {
			int i = move.getTo().getRow();
			if (board.isWhiteToMove() && i == 7) {
				// transform
				board.setPiece(move.getTo(), Piece.WQ);
			}
			if (!board.isWhiteToMove() && i == 0) {
				// transform
				board.setPiece(move.getTo(), Piece.BQ);
			}
		}
		
		board.setLastMove(move);
		//board.setWhiteToMove( !board.isWhiteToMove());
		board.toggleWhiteToMove();
		
		
//System.out.println(Evaluator.evaluate(board));
		return board;
	}	
	
	public boolean canCastleShort(byte piece){
		if(piece == Piece.WK){
			if(!isWhiteKingMoved && !isRookH1Moved ){
				return true;
			}
		} else if(piece == Piece.WK) {
			if (!isBlackKingMoved && !isRookH8Moved){
				return true;
			}
		}
		return false;
	}
	
	public boolean canCastleLong(byte piece){
		if(piece == Piece.WK){
			if(!isWhiteKingMoved && !isRookA1Moved ){
				return true;
			}
		} else if(piece == Piece.WK) {
			if (!isBlackKingMoved && !isRookA8Moved){
				return true;
			}
		}
		return false;
	}
	
	//Set and get the state of chess
	
	public void setCastledWhite(boolean isCastledWhite) {
		this.isCastledWhite = isCastledWhite;
	}
	
	public void setCastledBlack(boolean isCastledBlack) {
		this.isCastledBlack = isCastledBlack;
	}
	
	public boolean isCastledWhite() {
		return isCastledWhite;
	}

	public void setCalstledWhite(boolean b) {
		isCastledWhite = b;
	}
	
	public boolean isCastledBlack() {
		return isCastledBlack; 
	}
    
	public void setCalstledBlack(boolean b) {
		isCastledBlack = b;
	}
	
    public boolean isRookA1Moved() {
		return isRookA1Moved;
	}
    
	public void setRookA1Moved(boolean b) {
		isRookA1Moved = b;
	}
	
	public boolean isRookA8Moved() {
		return isRookA8Moved;
	}

	public void setRookA8Moved(boolean b) {
		isRookA8Moved = b;
	}
	
	public boolean isRookH1Moved() {
		return isRookH1Moved;
	}

	public void setRookH1Moved(boolean b) {
		isRookH1Moved = b;
	}
	
	public boolean isRookH8Moved() {
		return isRookH8Moved;
	}

	public void setRookH8Moved(boolean b) {
		isRookH8Moved = b;
	}
	
	public void setWhiteKingMoved(boolean isWhiteKingMoved) {
		this.isWhiteKingMoved = isWhiteKingMoved;
	}

	public boolean isWhiteKingMoved() {
		return isWhiteKingMoved;
	}

	public void setBlackKingMoved(boolean isBlackKingMoved) {
		this.isBlackKingMoved = isBlackKingMoved;
	}

	public boolean isBlackKingMoved() {
		return isBlackKingMoved;
	}
	
	public void toggleWhiteToMove(){
//System.out.println("change player\n" + isWhiteToMove);
		isWhiteToMove = !isWhiteToMove;
//System.out.println(isWhiteToMove);
	}
	
	public boolean isWhiteToMove() {
		return isWhiteToMove;
	}
	public void setWhiteToMove(boolean isWhiteToMove) {
		this.isWhiteToMove = isWhiteToMove;
	}
	//----------------------------------------
	private void setPiece(Position pos, byte piece) {
		setPiece(pos.getRow(), pos.getCol(), piece);
	}

	public byte getPiece(Position pos) {
		return getPiece(pos.getRow(), pos.getCol());
	}

	public byte getPiece(int row, int col){
		return fields[row][col];
	}
	
	public byte getPiece(byte index){
		return fields[index/LENGTH][index%LENGTH];	
	}
	
	public byte getColor(byte index){
		return Piece.getColor(getPiece(index));
	}
	
	public void setPiece(int row, int col, byte piece){
		fields[row][col] = piece;
	}
	
	public Move getLastMove() {
		return lastMove;
	}
	public void setLastMove(Move lastMove) {
		this.lastMove = lastMove;
	}	

    public byte[][] getFields() {
		return fields;
	}
    
	public void setFields(byte[][] fields) {
		this.fields = fields;
	}
		
	public byte getEnPassant() {
		return enPassant;
	}
	public void setEnPassant(byte enPassant) {
		this.enPassant = enPassant;
	}
		
	public long getHash() {
		return hash;
	}
	public void setHash() {
//		this.hash = Zobrist.hash(this);
	}
	
	@Override
    public String toString() {
        String s = "";
        for (int i = LENGTH - 1; i >= 0; i--) {
        	for (int j = 0; j < LENGTH; j++){
        		s += Piece.toChar(fields[i][j]) + " ";
        	}
        	s+="\n";
        }    
        return s;
    }
	public void write(){
		System.out.println(isWhiteToMove);
		System.out.println(toString());
	}
	
	public boolean isChecked(byte player){
		byte opponent;
		byte king;
		if(player == Piece.BLACK){
			opponent = Piece.WHITE;
			king = Piece.BK;
			
		}else{
			opponent = Piece.BLACK;
			king = Piece.WK;			
		}
		Position pos = findPiece(king);
		return isCoveredBy(opponent, pos.toByte());
	}
	
    public boolean isCoveredBy(byte player, byte sq)
    {
        // Check for kings
        for(byte threatPosition : MoveTables.kingTables[sq]){
            if( getColor(threatPosition) == player && Piece.getType(getPiece(threatPosition)) == Piece.KING){ 
                return true;
            }
        }
       
        // Check knights
        for(byte threatPosition : MoveTables.knightTables[sq]){
            if( getColor(threatPosition) == player && Piece.getType(getPiece(threatPosition)) == Piece.KING){ 
                return true;
            }
        }
        //check for straights ray: rook and Queen
        for(byte[] ray : MoveTables.rookTables[sq]){
            for(byte threatPosition : ray){
            	byte piece = getPiece(threatPosition);
            	if(piece != Piece.NONE){
	            	if( getColor(threatPosition) == player && 
	            			Piece.getType(piece) == Piece.ROOK&&
	            			Piece.getType(piece) == Piece.QUEEN){ 
	                    return true;
	                }else{
	                	break;
	                }
            	}
            }
		}
        //check for diagonal ray: bishop and Queen
        for(byte[] ray : MoveTables.bishopTables[sq]){
            for(byte threatPosition : ray){
            	byte piece = getPiece(threatPosition);
            	if(piece != Piece.NONE){
	            	if( getColor(threatPosition) == player && 
	            			Piece.getType(piece) == Piece.BISHOP&&
	            			Piece.getType(piece) == Piece.QUEEN){ 
	                    return true;
	                }else{
	                	break;
	                }
            	}
            }
		}        

        // Check pawn, reverse direction
		byte dir = 1;		
		
		if(player == Piece.WHITE){			
			dir = -1;
		}
		
		Position srcPos = new Position(sq);		
		if(srcPos.getCol() != 0){
			Position leftPos = new Position((byte)(srcPos.getRow() + dir), (byte) (srcPos.getCol() - 1));
            if( getColor(leftPos.toByte()) == player && Piece.getType(getPiece(leftPos)) == Piece.PAWN){ 
                return true;
            }
		}
		if(srcPos.getCol() != LENGTH - 1){
			Position rightPos = new Position((byte)(srcPos.getRow() + dir), (byte) (srcPos.getCol() + 1));
            if( getColor(rightPos.toByte()) == player && Piece.getType(getPiece(rightPos)) == Piece.PAWN){ 
                return true;
            }			
		}
		
        return false;
    }
    private Position findPiece(byte piece){
    	Position pos = null;
    	for(byte rank = 0; rank < LENGTH; rank++ ){
    		for(byte col = 0; col < LENGTH; col++){
    			if(getPiece(rank, col) == piece){
    				pos = new Position(rank, col);
    				return pos;
    			}
    		}
    	}
    	return pos;
    }
}
